/* ----------------------------------------------------------------------------
 * Copyright (c) Huawei Technologies Co., Ltd. 2013-2019. All rights reserved.
 * Description: implementation for telnet server and client.
 * Author: zhangyangyang
 * Create: 2013-01-01
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 * conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 * of conditions and the following disclaimer in the documentation and/or other materials
 * provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 * to endorse or promote products derived from this software without specific prior written
 * permission.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * --------------------------------------------------------------------------- */
/* ----------------------------------------------------------------------------
 * Notice of Export Control Law
 * ===============================================
 * Huawei LiteOS OS may be subject to applicable export control laws and regulations, which might
 * include those applicable to Huawei LiteOS of U.S. and the country in which you are located.
 * Import, export and usage of Huawei LiteOS in any manner by you shall be in compliance with such
 * applicable export control laws and regulations.
 * --------------------------------------------------------------------------- */

#include "los_config.h"
#ifdef LOSCFG_NET_TELNET
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "unistd.h"
#include "pthread.h"
#include <netinet/tcp.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/types.h>          /* See NOTES */
#include "lwip/sockets.h"
#include <lwip/inet.h>
#include <lwip/netif.h>
#include "los_task.h"
#include "telnet_dev.h"
#include "telnet_loop.h"
#include "hisoc/uart.h"
#include "console.h"
#include "sys/prctl.h"
#ifdef LOSCFG_SHELL
#include "shell.h"
#include "shcmd.h"
#include "shMsg.h"
#endif

/*lint -esym(457 459, OsTelnetClose, TelnetdForwardLoop, ReadFilter, telnet_cmd, TelnetdMain)*/

/* TELNET commands in RFC854 */
#define TELNET_SE   240 /* End of subnegotiation parameters */
#define TELNET_SB   250 /* Indicates that what follows is subnegotiation of the indicated option */
#define TELNET_WILL 251 /* Indicates the desire to perform the indicated option */
#define TELNET_WONT 252 /* Indicates the refusal to perform the indicated option */
#define TELNET_DO   253 /* Indicates the request for the other party to perform the indicated option */
#define TELNET_DONT 254 /* Indicates the demand for the other party to stop performing the indicated option. */
#define TELNET_IAC  255 /* Interpret as Command */

/* telnet options in IANA */
#define TELNET_ECHO   1  /* Echo */
#define TELNET_SGA    3  /* Suppress Go Ahead */
#define TELNET_NAWS  31  /* Negotiate About Window Size */
#define TELNET_NOP 0xf1  /* Unassigned in IANA, putty use this to keepalive */

#define LEN_IAC_CMD      2 /* Only 2 char: |IAC|cmd| */
#define LEN_IAC_CMD_OPT  3 /* Only 3 char: |IAC|cmd|option| */
#define LEN_IAC_CMD_NAWS 9 /* NAWS: |IAC|SB|NAWS|x1|x2|x3|x4|IAC|SE| */

/* server/client settings */
#define TELNET_TASK_STACK_SIZE 0x2000
#define TELNET_TASK_PRIORITY 9
#define IP_STRING_MAX_LEN 64

/* server settings */
#define TELNET_LISTEN_BACKLOG 128

/* client settings */
#define TELNET_CLIENT_POLL_TIMEOUT 2000
#define TELNET_CLIENT_READ_BUF_SIZE 256
#define TELNET_CLIENT_READ_FILTER_BUF_SIZE (8 * 1024)

int g_telnetClientFd;
int g_telnetSocketFd;
volatile UINT32 g_telnetMask = 0;
static unsigned int g_telentTaskId = 0;

/* filter out iacs from client stream */
static unsigned char* ReadFilter(unsigned char *src, int srcLen, int *dstLen)
{
    static unsigned char buf[TELNET_CLIENT_READ_FILTER_BUF_SIZE];
    unsigned char *dst = buf;
    int left = srcLen;

    while (left > 0) {
        if (*src != TELNET_IAC) {
            *dst = *src;
            dst++;
            src++;
            left--;
            continue;
        }

        /*
         * if starting with IAC, filter out IAC as following
         * |IAC|          --> skip
         * |IAC|NOP|...   --> ... : skip for putty keepalive etc.
         * |IAC|x|        --> skip
         * |IAC|IAC|x|... --> |IAC|... : skip for literal cmds
         * |IAC|SB|NAWS|x1|x2|x3|x4|IAC|SE|... --> ... : skip NAWS(unsupported)
         * |IAC|x|x|...   --> ... : skip unsupported IAC
         */

        if (left == 1) {
            break;
        }
        /* left no less than 2 */
        if (*(src + 1) == TELNET_NOP) {
            src += LEN_IAC_CMD;
            left -= LEN_IAC_CMD;
            continue;
        }

        if (left == LEN_IAC_CMD) {
            break;
        }
        /* left no less than 3 */
        if (*(src + 1) == TELNET_IAC) {
            *dst = TELNET_IAC;
            dst++;
            src += LEN_IAC_CMD;
            left -= LEN_IAC_CMD;
            continue;
        }
        if (*(src + 1) == TELNET_SB && *(src + LEN_IAC_CMD) == TELNET_NAWS) {
            if (left > LEN_IAC_CMD_NAWS) {
                src += LEN_IAC_CMD_NAWS;
                left -= LEN_IAC_CMD_NAWS;
                continue;
            }
            break;
        }
        src += LEN_IAC_CMD_OPT;
        left -= LEN_IAC_CMD_OPT;
    }

    if (dstLen != NULL) {
        *dstLen = dst - buf;
    }
    return buf;
}

ssize_t xwriten(int fd, const char *src, size_t srcLen)
{
    size_t nLeft;
    ssize_t nWritten;

    nLeft = srcLen;
    while (nLeft > 0) {
        nWritten = write(fd, (const char *)src, nLeft);
        if (nWritten < 0) {
            if (nLeft == srcLen) {
                return -1;
            }
            else {
                break;
            }
        } else if (nWritten == 0) {
            break;
        }
        nLeft -= nWritten;
        src += nWritten;
    }

    return (srcLen - nLeft);
}

int OsTelnetClose(void)
{
    unsigned int intSave;

    intSave = LOS_IntLock();
    if (g_telnetMask == 0) {
        LOS_IntRestore(intSave);
        return 0;
    }
    (void)telnet_dev_deInit();
    g_telnetMask = 0;
    LOS_IntRestore(intSave);
    return 0;
}

static void OsTelnetRelase(void)
{
    if (g_telnetClientFd  != 0) {
        close(g_telnetClientFd);
        g_telnetClientFd = 0;
    }

    if  (g_telnetSocketFd != 0) {
        close(g_telnetSocketFd);
        g_telnetSocketFd = 0;
    }
}

int OsTelnetdDeinit(void)
{
    if (g_telentTaskId == 0) {
        PRINT_ERR("telnet already close!\n");
        return -1;
    }

    OsTelnetRelase();
    (void)telneted_unregister();
    g_telentTaskId = 0;
    PRINTK("close telnet.\n");

    return 0;
}

int TelnetdInit(int port)
{
    int listenFd;
    int reuseAddr = 1;
    int ret;
    struct sockaddr_in inTelnetAddr;

    listenFd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenFd == -1) {
        PRINT_ERR("Telnet socket error.\n");
        return -1;
    }

    /* reuse listen port */
    ret = setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr));
    if (ret != 0) {
        PRINT_ERR("Telnet REUSEADDR error.\n");
        close(listenFd);
        return -1;
    }

    (void)memset_s(&inTelnetAddr, sizeof(struct sockaddr_in), 0, sizeof(struct sockaddr_in));
    inTelnetAddr.sin_family = AF_INET;
    inTelnetAddr.sin_addr.s_addr = INADDR_ANY;
    inTelnetAddr.sin_port = htons(port);

    if (bind(listenFd, (struct sockaddr *)&inTelnetAddr, sizeof(struct sockaddr_in)) == -1) {
        close(listenFd);
        PRINT_ERR("Telnet bind error.\n");
        return -1;
    }

    if (listen(listenFd, TELNET_LISTEN_BACKLOG) == -1) {
        close(listenFd);
        PRINT_ERR("Telnet listen error.\n");
        return -1;
    }

    return listenFd;
}

void PrepareClient(int clientFd)
{
    unsigned int intSave;
    int keepAlive = TELNET_KEEPALIVE;
    int keepIdle = TELNET_KEEPIDLE;
    int keepInterval = TELNET_KEEPINTV;
    int keepCnt = TELNET_KEEPCNT;
    const unsigned char doEcho[] = { TELNET_IAC, TELNET_DO, TELNET_ECHO };
    const unsigned char doNaws[] = { TELNET_IAC, TELNET_DO, TELNET_NAWS };
    const unsigned char willEcho[] = { TELNET_IAC, TELNET_WILL, TELNET_ECHO };
    const unsigned char willSga[] = { TELNET_IAC, TELNET_WILL, TELNET_SGA };

    intSave = LOS_IntLock();
    g_telnetClientFd = clientFd;
    (void)telnet_dev_init(clientFd);
    g_telnetMask = 1;
    LOS_IntRestore(intSave);

    /* negotiate with client */
    (void)xwriten(clientFd, (char*)doEcho, sizeof(doEcho));
    (void)xwriten(clientFd, (char*)doNaws, sizeof(doNaws));
    (void)xwriten(clientFd, (char*)willEcho, sizeof(willEcho));
    (void)xwriten(clientFd, (char*)willSga, sizeof(willSga));

    /* enable TCP keepalive to check whether telnet link is alive */
    if (setsockopt(clientFd, SOL_SOCKET, SO_KEEPALIVE, (void*)&keepAlive, sizeof(keepAlive)) < 0) {
        perror("telnet setsockopt SO_KEEPALIVE");
    }
    if (setsockopt(clientFd, IPPROTO_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle)) < 0) {
        perror("telnet setsockopt TCP_KEEPIDLE");
    }
    if (setsockopt(clientFd, IPPROTO_TCP, TCP_KEEPINTVL, (void*)&keepInterval, sizeof(keepInterval)) < 0) {
        perror("telnet setsockopt TCP_KEEPINTVL");
    }
    if (setsockopt(clientFd, IPPROTO_TCP, TCP_KEEPCNT, (void*)&keepCnt, sizeof(keepCnt)) < 0) {
        perror("telnet setsockopt TCP_KEEPCNT");
    }
}

int TelnetdForwardLoop(int clientFd)
{
    struct pollfd pollFd;
    int ret;
    int nRead;
    int len;
    unsigned char buf[TELNET_CLIENT_READ_BUF_SIZE];
    unsigned char *toTty = NULL;

    if (clientFd < 0) {
        PRINT_ERR("fd is error value: %d.\n", clientFd);
        return -1;
    }
    (void)prctl(PR_SET_NAME, "telnet_loop", 0, 0, 0);
    PrepareClient(clientFd);

    while (1) {
        pollFd.fd = clientFd;
        pollFd.events = POLLIN | POLLRDHUP;
        pollFd.revents = 0;

        ret = poll(&pollFd, 1, TELNET_CLIENT_POLL_TIMEOUT);
        if (ret < 0) {
            break;
        } else if (ret > 0) {
            /* connection reset, maybe keepalive failed or reset by peer */
            if (pollFd.revents & (POLLERR | POLLHUP)) {
                PRINTK("Telnet connection reset\n");
                break;
            }

            if (pollFd.revents & POLLRDHUP) {
                PRINTK("Telnet connection closed\n");
                break;
            }

            if (pollFd.revents & POLLIN) {
                nRead = read(clientFd, buf, sizeof(buf));
                if (nRead <= 0) {
                    PRINTK("\nTelnet client shutdown\n") ;
                    break;
                } else {
                    toTty = ReadFilter(buf, nRead, &len);
                    if (len > 0) {
                        (void)telnet_tx((char *)toTty, len);
                        continue;
                    }
                }
            }
        }
    }
    (void)OsTelnetClose();
    close(clientFd);
    g_telnetClientFd = 0;
    return 0;
}

void InitForwardLoopAttr(pthread_attr_t* threadAttr)
{
    (void)pthread_attr_init(threadAttr);
    threadAttr->inheritsched = PTHREAD_EXPLICIT_SCHED;
    threadAttr->schedparam.sched_priority = TELNET_TASK_PRIORITY;
    threadAttr->detachstate = PTHREAD_CREATE_DETACHED;
    (void)pthread_attr_setstacksize(threadAttr, TELNET_TASK_STACK_SIZE);
}

void TelnetdAcceptLoop(int listenFd)
{
    int clientFd;
    int clientPort;
    int len;
    int err;
    char clientAddr[IP_STRING_MAX_LEN] = {0};
    struct sockaddr_in inTelnetAddr;
    pthread_t tmp;
    pthread_attr_t useAttr;

    InitForwardLoopAttr(&useAttr);

    len = sizeof(inTelnetAddr);
    g_telnetSocketFd = listenFd;
    while (1) {
        (void)memset_s(&inTelnetAddr, sizeof(inTelnetAddr), 0, sizeof(inTelnetAddr));
        clientFd = accept(listenFd, (struct sockaddr *)&inTelnetAddr, (socklen_t *)&len);

        if (clientFd < 0) {
            if (errno == EINTR) {
                PRINTK("Telnet accept error: %s\n", strerror(errno));
            } else if (errno == ECONNABORTED) {
                PRINTK("Telnet accept error: %s\n", strerror(errno));
                (void)close(listenFd);
                g_telnetSocketFd = 0;
                return;
            }
            continue;
        }

        if (g_telnetClientFd) {
            /* alreay connected and support only one */
            close(clientFd);
            continue;
        } else {
            g_telnetClientFd = clientFd;
        }

        if (clientFd >= FD_SETSIZE - 1) {
            PRINT_ERR("Accept telnet clients is Max!\n");
            continue;
        }

        err = strncpy_s(clientAddr, sizeof(clientAddr), inet_ntoa(inTelnetAddr.sin_addr), (sizeof(clientAddr)) - 1);
        if (err == EOK) {
            clientPort = ntohs(inTelnetAddr.sin_port);
            PRINTK("Accept telnet client connection from %s:%d\n", clientAddr, clientPort);
        }

        if (pthread_create(&tmp, &useAttr,
                           (void *(*)(void *))TelnetdForwardLoop, (void *)(unsigned long)clientFd) != 0) {
            PRINT_ERR("client handle thread creat failed\n");
            continue;
        }
        PRINTK("client handle thread creat success!\n");
    }
}

int TelnetdMain(void)
{
    int sock;
    int ret;
    sock = TelnetdInit(TELNETD_PORT);
    if (sock == -1) {
        PRINT_ERR("Telnet init error.\n");
        return -1;
    }

    ret = telneted_register();
    if (ret != 0) {
        PRINT_ERR("Telnet register error.\n");
        return -1;
    }

    PRINTK("init telnet.\n");
    TelnetdAcceptLoop(sock);
    return 0;
}

int OsTelnetdTaskInit(void)
{
    int ret;
    TSK_INIT_PARAM_S initParam = {
        (TSK_ENTRY_FUNC)TelnetdMain, TELNET_TASK_PRIORITY, { 0 },
        TELNET_TASK_STACK_SIZE, "telnet_server", LOS_TASK_STATUS_DETACHED
    };

    if (g_telentTaskId != 0) {
        PRINT_ERR("telnet server already init! \n");
        return -1;
    }

    ret = LOS_TaskCreate(&g_telentTaskId, &initParam);
    if (ret != LOS_OK) {
        return ret;
    }

    return ret;
}

int telnet_cmd(UINT32 argc, UINT8 **argv)
{
    unsigned int i;
    if (argc == 0) {
        PRINTK("Usage: telnet [OPTION]...\n");
        PRINTK("Start or close telnet server.\n");
        PRINTK("\n  on       Init the telnet server\n");
        PRINTK("  off      Deinit the telnet server\n");
        return 0;
    }
    else if (argc == 1){
        if (strcmp((const char *)argv[0], "on") == 0) {
            /* telnet on: try to start telnet server task */
            if (tcpip_init_finish == 0) {
                PRINT_ERR("tcpip_init have not been called\n");
                return -1;
            }
            (void)OsTelnetdTaskInit();
            return 0;
        } else if (strcmp((const char *)argv[0], "off") == 0) {
            /* telnet off: try to stop clients, then stop server task  */
            if (g_telentTaskId == 0) {
                PRINTK("Please start up telnet server first\n");
                return -1;
            }
            PRINTK("Telnet server shutdown\n");
            (void)OsTelnetClose();
            (void)OsTelnetdDeinit();
            return 0;
        }
    }

    for (i = 0; i < argc; i++) {
        PRINTK("telnet can't access %s\n", argv[i]);
    }

    return 0;
}


#ifdef LOSCFG_SHELL
SHELLCMD_ENTRY(telnet_shellcmd, CMD_TYPE_EX, "telnet", 2, (CMD_CBK_FUNC)telnet_cmd); /*lint !e19*/
#endif /* LOSCFG_SHELL */
#endif
